package com.yeskery.nut.extend.jdbc;

import com.yeskery.nut.application.ServerEventContext;
import com.yeskery.nut.bean.ApplicationContext;
import com.yeskery.nut.bean.NoSuchBeanException;
import com.yeskery.nut.core.NutException;
import com.yeskery.nut.plugin.ApplicationContextSupportBasePlugin;
import com.yeskery.nut.plugin.PluginBeanMetadata;
import com.yeskery.nut.plugin.ServerEventPlugin;
import com.yeskery.nut.transaction.TransactionRegistry;
import com.yeskery.nut.util.ClassLoaderUtils;
import com.yeskery.nut.util.StringUtils;
import com.yeskery.nut.util.cache.ConnectionCreator;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * JDBC数据库插件
 * @author sprout
 * 2022-06-02 18:10
 */
public class JdbcPlugin extends ApplicationContextSupportBasePlugin implements ServerEventPlugin {

    /** 日志对象 */
    private static final Logger logger = Logger.getLogger(JdbcPlugin.class.getName());

    /** JDBC驱动类名称 */
    private String className;

    /** JDBC URL */
    private String url;

    /** JDBC 用户名 */
    private String username;

    /** JDBC 密码 */
    private String password;

    /** 连接缓存大小 */
    private int cacheSize;

    /** 数据源对象 */
    private DataSource dataSource;

    /** 存在应用上下文数据源 */
    private boolean applicationContextDataSourceExist = false;

    /**
     * 构建一个JDBC数据库插件
     */
    public JdbcPlugin() {
    }

    /**
     * 构建一个JDBC数据库插件
     * @param dataSource 数据源对象
     */
    public JdbcPlugin(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 构建一个JDBC数据库插件
     * @param className JDBC驱动类名称
     * @param url JDBC URL
     * @param username JDBC 用户名
     * @param password JDBC 密码
     */
    public JdbcPlugin(String className, String url, String username, String password) {
        this(className, url, username, password, 10);
    }

    /**
     * 构建一个JDBC数据库插件
     * @param className JDBC驱动类名称
     * @param url JDBC URL
     * @param username JDBC 用户名
     * @param password JDBC 密码
     * @param cacheSize 连接缓存大小
     */
    public JdbcPlugin(String className, String url, String username, String password, int cacheSize) {
        if (StringUtils.isEmpty(className)) {
            throw new NutException("Driver Class Must Not Be Null.");
        }
        this.className = className;
        if (StringUtils.isEmpty(url)) {
            throw new NutException("JDBC Url Must Not Be Null.");
        }
        this.url = url;
        if (StringUtils.isEmpty(username)) {
            throw new NutException("JDBC Username Must Not Be Null.");
        }
        this.username = username;
        if (StringUtils.isEmpty(password)) {
            throw new NutException("JDBC Password Must Not Be Null.");
        }
        this.password = password;
        if (cacheSize < 0) {
            throw new NutException("Cache Size Must Greater Than 0.");
        }
        this.cacheSize = cacheSize;
    }

    @Override
    protected Collection<PluginBeanMetadata> getRegisterPluginBeanMetadata() {
        ApplicationContext applicationContext = getApplicationContext();
        DataSource dataSource = this.dataSource;
        try {
            dataSource = applicationContext.getBean(DataSource.class);
            applicationContextDataSourceExist = true;
        } catch (NoSuchBeanException e) {
            if (this.dataSource == null) {
                if (StringUtils.isEmpty(className) || StringUtils.isEmpty(url)
                        || StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
                    throw new NutException("Invalid DataSource Configuration.");
                }
                try {
                    Class.forName(className, true, ClassLoaderUtils.getClassLoader());
                    ConnectionCreator<Connection> connectionCreator = () -> {
                        try {
                            return DriverManager.getConnection(url, username, password);
                        } catch (SQLException e1) {
                            throw new NutException("JDBC Connection Create Fail.", e1);
                        }
                    };

                    dataSource = cacheSize <= 0 ? new WrapBasicDataSource(connectionCreator)
                            : new WrapCacheDataSource(connectionCreator, cacheSize);
                } catch (ClassNotFoundException e1) {
                    throw new NutException("JDBC Driver Class Not Found.", e1);
                }
            }
        }
        TransactionRegistry transactionRegistry = applicationContext.getBean(TransactionRegistry.class);
        Collection<PluginBeanMetadata> pluginBeanMetadataCollection = new ArrayList<>(2);
        if (!applicationContextDataSourceExist) {
            pluginBeanMetadataCollection.add(new PluginBeanMetadata("dataSource", dataSource, DataSource.class));
        }
        pluginBeanMetadataCollection.add(new PluginBeanMetadata("jdbcTemplate",
                new JdbcTemplateImpl(dataSource, transactionRegistry.getTransactionManager()), JdbcTemplate.class, JdbcTemplateImpl.class));
        return pluginBeanMetadataCollection;
    }

    @Override
    public void beforeClose(ServerEventContext serverEventContext) {
        if (!applicationContextDataSourceExist && dataSource != null && dataSource instanceof WrapCacheDataSource) {
            try {
                ((WrapCacheDataSource) dataSource).close();
            } catch (IOException e) {
                logger.logp(Level.SEVERE, this.getClass().getName(), "beforeClose",
                        "Wrap Cache DataSource Close Fail.", e);
            }
        }
    }
}
